package jdesktop.appbar.win32;

import javax.swing.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An {@code InvocationHandler} that invokes
 * the method later using the {@link javax.swing.SwingUtilities#invokeLater(Runnable)}
 * method.
 * <p>
 * This is appropriate when asynchronous execution is acceptable, such as when the method has
 * a void return type, and delayed semantics are acceptable.
 */
public class SwingInvokeLaterHandler implements InvocationHandler
{
    protected final Logger log = Logger.getLogger(getClass().getName());

    private Object  target;

    public SwingInvokeLaterHandler(Object target)
    {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        MethodInvocation invocation = newMethodInvocation(proxy, method, args);
        return dispatch(invocation);
    }

    protected Object dispatch(MethodInvocation invocation)
    {
        checkInvocation(invocation);
        SwingUtilities.invokeLater(invocation);
        return null;
    }

    protected void checkInvocation(MethodInvocation invocation)
    {
        if (invocation.getMethod().getReturnType()!=Void.TYPE)
            throw new IllegalArgumentException(String.format("Cannot run invocation later. Method must have a void return type. %s", invocation));
    }

    protected MethodInvocation newMethodInvocation(Object proxy, Method method, Object[] args)
    {
        // invoke the method on the target
        return new MethodInvocation(target, method, args);
    }

    static class MethodInvocation implements Runnable
    {
        private Object target;
        private Method method;
        private Object[] args;

        MethodInvocation(Object target, Method method, Object[] args)
        {
            this.target = target;
            this.method = method;
            this.args = args;
        }

        public void run()
        {
            Throwable t = null;
            try
            {
                method.invoke(target, args);
            }
            catch (IllegalAccessException e)
            {
                t = e;
            }
            catch (InvocationTargetException e)
            {
                t = e;
            }
            if (t!=null)
            {
                getLog().log(Level.SEVERE, String.format("Exception executing invocation %s", this), t);
                throw wrap(t);
            }
        }

        protected Logger getLog()
        {
            return Logger.getLogger(getClass().getName());
        }

        protected RuntimeException wrap(Throwable t)
        {
            throw new RuntimeException(t);
        }

        public Method getMethod()
        {
            return method;
        }
    }
    

}

